package emulator.shell.GUI.debugger;

import java.awt.BorderLayout;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.*;
import java.util.Observable;
import java.util.Observer;
import java.util.prefs.Preferences;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.JToolBar;

import emulator.assembler.Assembler;
import emulator.hardware.nmos6502.Cpu6502;

@SuppressWarnings("serial")
public class DebugPanel extends JPanel
{
	private Frame parent;
	private JToolBar toolbar;
	private CodePanel code_panel;
	private StackPanel stack_panel;
	private JButton button_go;
	private JButton button_halt;
	private JButton button_step_into;
	private JButton button_step_over;
	private JButton button_step_out;
	private JTextField textfield_search;
	private JButton button_search;
	private JButton button_symbols;
	private CpuRegisterPanel cpu_registers;
	private CpuStateObserver cpu_state_observer;
	private Cpu6502 cpu;
	
	public DebugPanel(Frame parent, Assembler assembler, Cpu6502 cpu, byte[] memory)
	{
		super();
		
		this.cpu = cpu;

		cpu_state_observer = new CpuStateObserver();
		cpu.addRunningStateObserver(cpu_state_observer);

		setLayout(new BorderLayout());
		
		toolbar = createToolbar();
		cpu_registers = new CpuRegisterPanel(cpu);
		code_panel = new CodePanel(assembler, cpu, memory, this);
		code_panel.setFocusable(true);
		stack_panel = new StackPanel(cpu.getStack(), code_panel);
		
		JSplitPane split_pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
												code_panel, stack_panel);
		split_pane.setDividerLocation(loadDividerLocation());
		split_pane.addPropertyChangeListener("dividerLocation", new DividerListener()); 
		
		add(toolbar, BorderLayout.PAGE_START);
		
		JPanel panel = new JPanel();
		panel.setLayout(new BorderLayout());
		panel.add(cpu_registers, BorderLayout.PAGE_START);
        panel.add(split_pane, BorderLayout.CENTER);
        
        add(panel, BorderLayout.CENTER);
        
        updateRunState(cpu.isRunning());
	}
	
	private int loadDividerLocation() {
		Preferences cfg = Preferences.userNodeForPackage(this.getClass());
		return cfg.getInt("DebugPanel.divider", 62);
	}

	public void saveDividerLocation(int location) {
		Preferences cfg = Preferences.userNodeForPackage(this.getClass());
	    cfg.putInt("DebugPanel.divider", location);
	}

	public void shutdown()
	{
		cpu.deleteRunningStateObserver(cpu_state_observer);
	}

	private JToolBar createToolbar() {
		button_go = makeNavigationButton("images/run_icon.png",  "start running", "go",   new Go());
		button_halt = makeNavigationButton("images/stop_icon.png", "stop running",  "halt", new Halt());
		button_step_into = makeNavigationButton("images/step_into.png", "step into",  "into", new StepInto());
		button_step_over = makeNavigationButton("images/step_over.png", "step over",  "over", new StepOver());
		button_step_out = makeNavigationButton("images/step_out.png", "step out",  "out", new StepOut());
		
		textfield_search = new JTextField();
		textfield_search.addActionListener(new Search());
		button_search = makeNavigationButton("images/search_icon.png", "search",  "search", new Search());
		
		button_symbols = makeNavigationButton("images/load_symbols.png", "load symbols", "symbols", new LoadSymbols());

		JToolBar toolbar = new JToolBar();
		toolbar.add(button_go);
		toolbar.add(button_halt);
		toolbar.add(button_step_into);
		toolbar.add(button_step_over);
		toolbar.add(button_step_out);
		toolbar.addSeparator();
		toolbar.add(textfield_search);
		toolbar.add(button_search);
		toolbar.addSeparator();
		toolbar.add(button_symbols);
		
		return toolbar;
	}

    protected JButton makeNavigationButton(	String imageName,
								            String toolTipText,
								            String altText,
								            ActionListener action_listener)
    {
		//Create and initialize the button.
		JButton button = new JButton();
		button.setToolTipText(toolTipText);
		button.addActionListener(action_listener);
		
		if (imageName != null) {                      //image found
			button.setIcon(new ImageIcon(imageName, altText));
		} else {                                     //no image found
			button.setText(altText);
			System.err.println("Resource not found: " + imageName);
		}

		return button;
    }

	private void updateRunState(boolean run_state) 
	{
		if (run_state)
		{
			button_go.setEnabled(false);
			button_halt.setEnabled(true);
			button_step_into.setEnabled(false);
			button_step_over.setEnabled(false);
			button_step_out.setEnabled(false);
		}
		else
		{
			button_go.setEnabled(true);
			button_halt.setEnabled(false);
			button_step_into.setEnabled(true);
			button_step_over.setEnabled(true);
			button_step_out.setEnabled(true);
		}
		cpu_registers.updateValues(run_state);
		code_panel.updateRunState(run_state);
		stack_panel.updateRunState(run_state);
	}

	public void onPreRun() {
		cpu_registers.transferValuesToCPU();
	}
	
	class DividerListener implements PropertyChangeListener
	{
		public void propertyChange(PropertyChangeEvent e) {
			Number value = (Number) e.getNewValue();
			saveDividerLocation(value.intValue());
		}
	}

	public class Go implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			onPreRun();
			code_panel.go();
		}
	}

	public class Halt implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			code_panel.halt();
		}
	}

	public class StepInto implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			onPreRun();
			code_panel.stepInto();
		}
	}

	public class StepOver implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			onPreRun();
			code_panel.stepOver();
		}
	}

	public class StepOut implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			onPreRun();
			code_panel.stepOut();
		}
	}

	public class Search implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			String search_text = textfield_search.getText();
			if (!search_text.isEmpty())
				code_panel.search(search_text);
		}
	}
	
	public class LoadSymbols implements ActionListener {
		@Override
		public void actionPerformed(ActionEvent event) {
			FileDialog dialog = new FileDialog(parent, "Load Symbols from File", FileDialog.LOAD);
			dialog.setVisible(true);
			if (dialog.getDirectory() != null && dialog.getFile() != null)
			{
				String selected_file = dialog.getDirectory() + dialog.getFile();
				if (!selected_file.isEmpty())
				{
					code_panel.loadSymbols(selected_file);
				}
			}
		}
		
	}

	class CpuStateObserver implements Observer
	{
		class CpuStateEvent implements Runnable
		{
			private boolean run_state;
			
			public CpuStateEvent(boolean run_state)
			{
				this.run_state = run_state;
			}
			
			@Override
			public void run() {
				updateRunState(run_state);
			}
		}
		
		@Override
		public void update(Observable state, Object run_state) {
		    javax.swing.SwingUtilities.invokeLater(new CpuStateEvent((Boolean)run_state));
		}
	}
}
